package evs.location;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class EntityDom
{
    public String name;
    public String value;
    
    public List<EntityDom> children = new ArrayList<EntityDom>();
    public EntityDom parent;
    public boolean isCollection = false;
    
    public EntityDom getChild( String name )
    {
        for ( EntityDom child : children )
        {
            if ( child.name.equals( name ) )
                return child;
        }
        
        return null;
    }
    
    public void addChild( EntityDom child )
    {
        this.children.add( child );
        child.parent = this;
    }
    
    public void removeChild( EntityDom child )
    {
    	assert this.children.contains( child );
    	
        this.children.remove( child );
        child.parent = null;
    }
    
    public String toXML()
    {
        StringBuilder sb = new StringBuilder();
        this.printXml( sb, 0, 2 );
        return sb.toString();
    }
    
    public static EntityDom fromXML( String input ) throws EvsException
    {
        InputSource is = new InputSource( new StringReader( input ) );
        return fromXML( is );
    }
    
    public static EntityDom fromXML( InputSource input ) throws EvsException
    {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setNamespaceAware( false );
        factory.setXIncludeAware( false );
                
        try
        {
            SAXParser parser = factory.newSAXParser();
            
            XmlSaxHandler handler = new XmlSaxHandler();
            parser.parse( input, handler );
            
            return handler.root;
        }
        catch ( ParserConfigurationException e )
        {
            throw new EvsException( e );
        }
        catch ( SAXException e )
        {
            throw new EvsException( HttpServletResponse.SC_BAD_REQUEST, e.getMessage() );
        }
        catch ( IOException e )
        {
            throw new EvsException( e );
        }
    }
    
    private static class XmlSaxHandler extends DefaultHandler
    {
        public EntityDom root;
        private EntityDom _current;
        
        @Override
        public void startElement( String uri, String localName, String qName, Attributes attributes )
                        throws SAXException
        {
            EntityDom element = new EntityDom();
            element.name = qName;
            
            if ( _current == null )
            {
                this.root = element;
            }
            else
            {
                _current.addChild( element );
            }
            
            _current = element;
        }
        
        @Override
        public void endElement( String uri, String localName, String qName ) throws SAXException
        {
            _current = _current.parent;
        }

        @Override
        public void characters( char[] ch, int start, int length ) throws SAXException
        {
        	String s = new String( ch, start, length );
        	
        	if ( _isOnlyWhiteSpace(s) )
        		return;
        	
            _current.value = s;
        }
        
        private static boolean _isOnlyWhiteSpace( String s )
        {
        	for ( int i = 0; i < s.length(); ++i )
        		if ( !Character.isWhitespace( s.charAt(i) ) )
        			return false;
        	
        	return true;
        }
    }
    
    @Override
    public String toString()
    {
        return this.toXML();
    }
    
    public void printXml( StringBuilder sb, int indention, int indentStep )
    {
        boolean isSimple = this.children.isEmpty() && this.value == null;
        
        if ( isSimple )
        {
            for ( int i = 0; i < indention; ++i )
                sb.append( ' ' );
            
            sb.append( '<' ); sb.append( this.name ); sb.append( "/>\n" );
        }
        else
        {
            for ( int i = 0; i < indention; ++i )
                sb.append( ' ' );
            
            sb.append( '<' ); sb.append( this.name ); sb.append( ">" );
        
            if ( this.value != null )
            {
                sb.append( value );
            }
            else
            {
                sb.append( '\n' );
                
                for ( EntityDom child : this.children )
                    child.printXml( sb, indention + indentStep, indentStep );
                
                for ( int i = 0; i < indention; ++i )
                    sb.append( ' ' );
            }
            
            sb.append( "</" ); sb.append( this.name ); sb.append( ">\n" );
        }
    }
    
    public static EntityDom fromJSON( Reader input ) throws EvsException
    {
        JsonFactory factory = new JsonFactory();
        try
        {
            JsonParser jp = factory.createJsonParser( input );
            
            jp.nextToken(); // Start Object
            
            jp.nextToken(); // field name
            
            String name = jp.getText();
            
            jp.nextToken(); // Start Object
            
            return parseObject( jp, name );
        }
        catch ( JsonParseException e )
        {
            throw new EvsException( e );
        }
        catch ( IOException e )
        {
            throw new EvsException( e );
        }
    }
    
    private static EntityDom parseObject( JsonParser jp, String name ) 
        throws EvsException, JsonParseException, IOException
    {
        EntityDom dom = new EntityDom();
        dom.name = name;
        
        if ( jp.getCurrentToken() != JsonToken.START_OBJECT )
        {
            if ( jp.getCurrentToken() != JsonToken.VALUE_NULL )
                dom.value = jp.getText();
            
            return dom;
        }
        
        while ( jp.nextToken() != JsonToken.END_OBJECT )
        {
            switch ( jp.getCurrentToken() )
            {       
                case VALUE_FALSE:
                case VALUE_TRUE:
                case VALUE_NUMBER_FLOAT:
                case VALUE_NUMBER_INT:
                case VALUE_STRING:
                case VALUE_EMBEDDED_OBJECT:
                case VALUE_NULL:
                    dom.addChild( parseObject( jp, jp.getCurrentName() ) );
                    break;
                    
                case START_OBJECT:
                    dom.addChild( parseObject( jp, jp.getCurrentName() ) );
                    break;
                    
                case START_ARRAY:
                    dom.addChild( parseArray( jp, jp.getCurrentName() ) );
                    break;
            }
        }
        
        assert dom.name != null;
        
        return dom;
    }

    private static EntityDom parseArray( JsonParser jp, String name ) 
        throws JsonParseException, IOException, EvsException
    {
        EntityDom dom = new EntityDom();
        dom.name = name;
        dom.isCollection = true;
        
        while ( jp.nextToken() != JsonToken.END_ARRAY )
        {
            if ( jp.getCurrentToken() == JsonToken.START_OBJECT )
            {
                if ( jp.nextToken() != JsonToken.FIELD_NAME )
                    throw new EvsException( "EntityDom.parseArray() - Expected field name at " + jp.getTokenLocation() );
                
                String typeName = jp.getCurrentName();
                
                if ( jp.nextToken() != JsonToken.START_OBJECT )
                    throw new EvsException( "EntityDom.parseArray() - Expected start of object at " + jp.getTokenLocation() );
                
                dom.addChild( parseObject( jp, typeName ) );
                
                if ( jp.nextToken() != JsonToken.END_OBJECT )
                    throw new EvsException( "EntityDom.parseArray() - Expected end of object at " + jp.getTokenLocation() );
            }
            else
            {
                String typeName = "value";
                
                switch ( jp.getCurrentToken() )
                {
                    case VALUE_TRUE: typeName = "boolean"; break;
                    case VALUE_FALSE: typeName = "boolean"; break;
                    case VALUE_NUMBER_INT: typeName = "int"; break;
                    case VALUE_NUMBER_FLOAT: typeName = "float"; break;
                    case VALUE_NULL: typeName = "null"; break;
                }
                
                dom.addChild( parseObject( jp, typeName ) );
            }
        }
        
        return dom;
    }
    
    public String toJson()
    {
        JsonFactory factory = new JsonFactory();
        StringWriter writer = new StringWriter();
        
        try
        {
            JsonGenerator jg = factory.createJsonGenerator( writer );
            jg.useDefaultPrettyPrinter();
            
            jg.writeStartObject();
            
            printJson( jg );
            
            jg.writeEndObject();
            
            jg.flush();
            writer.flush();
        }
        catch ( IOException e )
        {
        }
        
        return writer.toString();
    }
    
    public void printJson( JsonGenerator jg )
    {
        try
        {
            boolean isSimple = this.children.isEmpty() && !this.isCollection;
            
            if ( isSimple )
            {
                if ( this.value != null )
                {
                    jg.writeStringField( this.name, this.value );
                }
                else
                {
                    jg.writeNullField( this.name );
                }
            }
            else
            {
                jg.writeFieldName( this.name );
                
                if ( this.isCollection )
                {
                    jg.writeStartArray();
                    
                    for ( EntityDom child : this.children )
                    {
                        jg.writeStartObject();
                        child.printJson( jg );
                        jg.writeEndObject();
                    }
                    
                    jg.writeEndArray();
                }
                else
                {
                    jg.writeStartObject();
                    
                    for ( EntityDom child : this.children )
                    {
                        child.printJson( jg );
                    }
                    
                    jg.writeEndObject();
                }
            }
        }
        catch ( JsonGenerationException e )
        {
            e.printStackTrace();
        }
        catch ( IOException e )
        {
            e.printStackTrace();
        }
    }
    
}
